﻿using System;
using System.Collections.Generic;
using System.Linq;
using devtm.Aop.Copy;
using devtm.Aop.Emit;
using devtm.Aop.Event;
using devtm.Aop.Extensions;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace devtm.Aop.Convert
{


    public class InterceptorContainer
    {


        public InterceptorContainer()
        {
            BeforeCallList = new List<MethodDefinitionBox>();
            InterceptCallList = new List<MethodDefinitionBox>();
            AfterCallList = new List<MethodDefinitionBox>();
            CatchList = new List<MethodDefinitionBox>();
        }


        public MethodDefinitionBox Add(
            InterceptEnumEventType type, 
            MethodDefinition method, 
            Mono.Collections.Generic.Collection<Mono.Cecil.CustomAttributeNamedArgument> properties, 
            int hashcode, 
            TypeDefinition typeInterceptor)
        {

            MethodDefinitionBox retour = new MethodDefinitionBox(type, method, hashcode, typeInterceptor);

            switch (type)
            {

                case InterceptEnumEventType.BeforeCall:                    
                    if (properties  != null)
                        retour.Properties = properties;
                    this.BeforeCallList.Add(retour);
                    break;

                case InterceptEnumEventType.InterceptCall:                    
                    this.InterceptCallList.Add(retour);
                    break;

                case InterceptEnumEventType.AfterCall:
                    this.AfterCallList.Add(retour);
                    break;

                case InterceptEnumEventType.Catch:
                    this.CatchList.Add(retour);
                    break;

                default:
                    throw new NotImplementedException(type.ToString());
            }

            return retour;

        }


        private List<MethodDefinitionBox> BeforeCallList { get; set; }
        private List<MethodDefinitionBox> InterceptCallList { get; set; }
        private List<MethodDefinitionBox> AfterCallList { get; set; }
        private List<MethodDefinitionBox> CatchList { get; set; }


        /// <summary>
        /// Writes the method before call.
        /// </summary>
        /// <param name="gen">The gen.</param>
        /// <param name="args">The args.</param>
        /// <param name="methodAddProperty">The method add property.</param>
        /// <param name="_Interceptor">The _ interceptor.</param>
        public void WriteMethodBeforeCall(            
            CilWorker gen,
            EmitVariableReferenceExpression args,
            MethodReference methodAddProperty,
            TypeDefinition _Interceptor)
        {
            if (BeforeCallList.Count > 0)
                WriteMethodInterceptEntry(BeforeCallList, gen, args, methodAddProperty, _Interceptor);
        }
      

  
        public void WriteMethodInterceptCall(
            CilWorker gen,
            EmitVariableReferenceExpression args,
            MethodReference methodAddProperty,
            TypeDefinition _Interceptor)
        {
            if (InterceptCallList.Count > 0)
                WriteMethodInterceptEntry(InterceptCallList, gen, args, methodAddProperty, _Interceptor);
        }


        public void WriteMethodAfterCall(
          CilWorker gen,
          EmitVariableReferenceExpression args,
          MethodReference methodAddProperty,
          TypeDefinition _Interceptor)
        {
            if (AfterCallList.Count > 0)
                WriteMethodInterceptEntry(AfterCallList, gen, args, methodAddProperty, _Interceptor);
        }


        public void WriteMethodCatchCall(
            CilWorker gen,
            EmitVariableReferenceExpression args,
            MethodReference methodAddProperty,
            TypeDefinition _Interceptor)
        {
            if (CatchList.Count > 0)
                WriteMethodInterceptEntry(CatchList, gen, args, methodAddProperty, _Interceptor);
        }


        /// <summary>
        /// Writes the method intercept entry.
        /// </summary>
        /// <param name="lst">The LST.</param>
        /// <param name="gen">The gen.</param>
        /// <param name="args">The args.</param>
        /// <param name="methodAddProperty">The method add property.</param>
        /// <param name="_Interceptor">The _ interceptor.</param>
        private void WriteMethodInterceptEntry(
            List<MethodDefinitionBox> lst,
            CilWorker gen,
            EmitVariableReferenceExpression args,
            MethodReference methodAddProperty,
            TypeDefinition _Interceptor)
        {

            ModuleDefinition _module = gen.Body.Method.Module;

            foreach (MethodDefinitionBox i in lst)
            {

                MethodDefinition myMethod = i.Method;

                #region Send Properties
                
                if (i.Properties != null)
                {
                    foreach (Mono.Cecil.CustomAttributeNamedArgument arg in i.Properties)
                    {
                        gen.GetStockLocation(args.Variable);                //L_0026: ldloc.1 
                        gen.WriteInteger(i.Hashcode);               //L_0027: ldc.i4 0x12d80c
                        gen.Insert(OpCodes.Ldstr, arg.Name);                //L_002c: ldstr ""
                        gen.WriteCustomAttributeArgumentValue(arg.Argument);
                        gen.Insert(OpCodes.Callvirt, methodAddProperty);    //L_0036: callvirt instance void [devtm.Aop]devtm.Aop.Event.InterceptEventArgs::AddProperty(int32, string, object)
                    }
                }

                #endregion

                if ((myMethod.Attributes & Mono.Cecil.MethodAttributes.Static) == Mono.Cecil.MethodAttributes.Static)
                {
                    
                    myMethod = GetMethodFor(i, _Interceptor, _module);
                    //SequencePoint s1 = null;

                    //Instruction i1 = myMethod.Body.Instructions.FirstOrDefault();
                    //if (i1 != null)
                    //    s1 = i1.SequencePoint;
                        
                    gen.Insert(OpCodes.Ldarg_0);
                    gen.GetStockLocation(args);
                    gen.WriteInteger(i.Hashcode);

                    Instruction i2 = gen.Insert(OpCodes.Call, _module.Import(myMethod));
                    //if (s1 != null)
                    //    s1.CopyTo(i2);

                }
                else
                {

                    // Non static
                    PropertyDefinition callingProperty = GetTypeFor(i.TypeInterceptor, _Interceptor, _module);

                    gen.Insert(OpCodes.Call, callingProperty.GetMethod);
                    gen.Insert(OpCodes.Ldarg_0);
                    gen.GetStockLocation(args);
                    gen.WriteInteger(i.Hashcode);

                    //var m1 = _module.Import(item.Method);
                    gen.Insert(OpCodes.Callvirt, _module.Import(myMethod));

                }
            }
        }



        private MethodDefinition GetMethodFor(MethodDefinitionBox i, TypeDefinition _Interceptor, ModuleDefinition _module)
        {

            if (!string.IsNullOrEmpty(i.MethodName))
                foreach (MethodDefinition p1 in _Interceptor.Methods)
                    if (p1.Name == i.MethodName)
                        return p1;

            var source = i.Method;

            CopyMethod cm = new CopyMethod(source);

            var myMethod = cm.CloneMethod(source.DeclaringType, _Interceptor, true, i.MethodName);

            myMethod.Attributes = Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Static;

            source.CopyReferenceInstructionFromMethodBody(myMethod);
            

            return myMethod;

        }



        private PropertyDefinition GetTypeFor(TypeDefinition t, TypeDefinition _Interceptor, ModuleDefinition _module)
        {

            foreach (PropertyDefinition p1 in _Interceptor.Properties)
                if (p1.Name == t.Name)
                    return p1;

            FieldDefinition field1 = new FieldDefinition("_" + t.Name, Mono.Cecil.FieldAttributes.Private | Mono.Cecil.FieldAttributes.Static, _module.Import(t));
            PropertyDefinition p = new PropertyDefinition(t.Name, Mono.Cecil.PropertyAttributes.HasDefault, _module.Import(t));
            MethodDefinition m = p.GetMethod = new MethodDefinition("get_" + t.Name, Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Static, _module.Import(t));

            _Interceptor.Fields.Add(field1);
            _Interceptor.Methods.Add(m);
            _Interceptor.Properties.Add(p);


            MethodDefinition ctor2 = t.Methods.Where(c => c.Name == ".ctor").First();


            using (CilWorker gen = new CilWorker(m.Body))
            {

                // Preparing locals
                VariableDefinition v1 = gen.Declare(_module.Import(t), "v1");
                VariableDefinition b1 = gen.Declare(_module.Import(typeof(bool)), "b1");

                // Preparing labels            
                Label label28 = gen.DefineLabel();
                Label label36 = gen.DefineLabel();

                // Writing body
                gen.Emit(OpCodes.Nop);
                gen.Emit(OpCodes.Ldsfld, field1);
                gen.Emit(OpCodes.Ldnull);
                gen.Emit(OpCodes.Ceq);
                gen.Emit(OpCodes.Ldc_I4_0);
                gen.Emit(OpCodes.Ceq);
                gen.Emit(OpCodes.Stloc_1);
                gen.Emit(OpCodes.Ldloc_1);
                gen.Emit(OpCodes.Brtrue_S, label28);
                gen.Emit(OpCodes.Nop);
                gen.Emit(OpCodes.Newobj, _module.Import(ctor2));
                gen.Emit(OpCodes.Stsfld, field1);
                gen.Emit(OpCodes.Nop);

                gen.MarkLabel(label28, g => g.Emit(OpCodes.Ldsfld, field1));
                gen.Emit(OpCodes.Stloc_0);
                gen.Emit(OpCodes.Br_S, label36);

                gen.MarkLabel(label36, g => g.Emit(OpCodes.Ldloc_0));
                gen.Emit(OpCodes.Ret);

            }

            return p;

        }


        public bool ContainsEntry { get { return this.BeforeCallList.Count > 0; } }
        public bool ContainsExit { get { return this.AfterCallList.Count > 0; } }
        public bool ContainsIntercept { get { return this.InterceptCallList.Count > 0; } }
        public bool ContainsCatch { get { return this.CatchList.Count > 0; } }


        public bool ContainsElement { get { return ContainsEntry || ContainsExit || ContainsIntercept || ContainsCatch; } }


    }

}
